﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using HQPad.Models;
using HQPad.DAL;
using HQPad.Services;

namespace HQPad.Controllers
{ 
    public class SkillController : BaseController
    {

        public SkillController()
        {
        }

        //
        // GET: /Skill/

        public ViewResult Index(int? id)
        {
            IUnitOfWork context = new HQContext();
            IRulesService rulesService = new RulesService(context);
            ISkillService skillService = new SkillService(context);

            if (!id.HasValue)
                id = rulesService.GetDefaultRules().RuleSetID;

            RuleSet rules = rulesService.GetRules(id.Value);
            PopulateRuleVersion(rules);

            return View(skillService.GetRuleSkills(id.Value).ToList());
        }

        //
        // GET: /Skill/Details/5

        public ViewResult Details(int id)
        {
            IUnitOfWork context = new HQContext();
            ISkillService skillService = new SkillService(context);

            Skill skill = skillService.GetSkill(id);

            PopulateRuleVersion(skill);

            return View(skill);
        }

        public ViewResult Details(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            ISkillService skillService = new SkillService(context);

            Skill skill = skillService.GetSkill(ruleSetId);

            PopulateRuleVersion(skill);

            return View(skill);
        }

        //
        // GET: /Skill/Create

        public ActionResult Create(int? id)
        {
            IUnitOfWork context = new HQContext();
            IRulesService rulesService = new RulesService(context);
            ISkillService skillService = new SkillService(context);

            if (!id.HasValue)
                id = rulesService.GetDefaultRules().RuleSetID;

            RuleSet rules = rulesService.GetRules(id.Value);
            PopulateRuleVersion(rules);

            PopulateSkillTypeDropdown(null);

            ViewBag.Tables = skillService.GetPointsTables(id.Value).ToList();

            return View();
        } 

        //
        // POST: /Skill/Create

        [HttpPost]
        public ActionResult Create(Skill skill, FormCollection formCollection)
        {
            IUnitOfWork context = new HQContext();
            ISkillService skillService = new SkillService(context);
            IRulesService rulesService = new RulesService(context);

            string typeOfSkill = formCollection["TypeOfSkill"];
            ModelState.SetModelValue("TypeOfSkill", new ValueProviderResult(typeOfSkill, typeOfSkill, new CultureInfo("en-GB")));

            skill.LastUpdated = DateTime.Now;
            skill.LastUpdatedByUserID = 1; // todo: hook in user system.

            // Create a skill slug from the skill name.
            string slug = skillService.CreateSkillSlug(skill.Name);

            // Try and get a skill with this slug to check for uniqueness.
            Skill existingSkill = skillService.GetSkill(skill.RuleSetID, slug);

            if (existingSkill != null)
            {
                ModelState.AddModelError("Slug", string.Format("Unable to save changes. The generated skill slug {0} already exists in the system, try a different skill name.", slug));
            }
            else
            {
                ModelState.SetModelValue("Slug", new ValueProviderResult(slug, slug, new CultureInfo("en-GB")));
                skill.Slug = slug;
                ModelState["Slug"].Errors.Clear();
            }

            TryUpdateModel(skill);

            if (ModelState.IsValid)
            {
                try
                {

                    skillService.Add(skill);

                    if (CreateCosts(context, skill, formCollection, skill.RuleSetID))
                    {
                        context.Save();
                        return RedirectToAction("Index");
                    }
                }
                catch (DataException)
                {
                    //Log the error (add a variable name after DataException)
                    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
                }
            }

            RuleSet rules = rulesService.GetRules(skill.RuleSetID);
            PopulateRuleVersion(rules);

            PopulateSkillTypeDropdown(skill.TypeOfSkill);

            ViewBag.Tables = skillService.GetPointsTables(skill.RuleSetID).ToList();

            //ViewBag.SpellLevelBonus = formCollection["spellLevel"];

            return View(skill);
        }
        
        public ActionResult Edit(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            ISkillService skillService = new SkillService(context);
            IBonusService bonusService = new BonusService(context);

            Skill skill = skillService.GetSkill(ruleSetId, slug);
            List<Bonus> skillBonuses = bonusService.GetSkillBonuses(skill.SkillID).ToList();

            ViewBag.BonusList = skillBonuses;

            PopulateCostData(skill);

            PopulateSkillTypeDropdown(skill.TypeOfSkill);

            PopulateRequirementsData(context, skill);

            PopulateSkillListDropdown(context, skill);

            PopulateGuildListDropdown(context, skill);

            PopulateBonusTypeDropdown(null);

            PopulateRuleVersion(skill);

            PopulateSkillBonusValueTypeList(context, ruleSetId);

            PopulateQuantityList();

            return View(skill);
        }

        //
        // POST: /Skill/Edit/5

        [HttpPost]
        public ActionResult Edit(Skill skill, FormCollection formCollection)
        {
            IUnitOfWork context = new HQContext();
            skill.LastUpdated = DateTime.Now;
            // todo: set last updated user.
            skill.LastUpdatedByUserID = 0;

            // Check the skill slug is unique.

            if (ModelState.IsValid)
            {
                try
                {
                    if (UpdateCosts(context, skill, formCollection))
                    {
                        ISkillService skillService = new SkillService(context);
                        skillService.UpdateSkill(skill);

                        context.Save();

                        return RedirectToAction("Index", new {id = skill.RuleSetID});
                    }
                }
                catch (DataException)
                {
                    //Log the error (add a variable name after DataException)
                    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
                }

                return RedirectToAction("Edit", new {id = skill.SkillID});
            }
            return View(skill);
        }

        public ActionResult Delete(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            ISkillService skillService = new SkillService(context);

            Skill skill = skillService.GetSkill(ruleSetId, slug);

            if (skill != null)
            {
                skillService.DeleteSkill(skill);
                context.Save();
            }
            
            return RedirectToAction("Index", new { id = skill.RuleSetID });
        }

        public ActionResult DeleteRequirement(string slug, int id)
        {
            //if (!id.HasValue)
            //    return RedirectToAction("edit", new { slug = slug });
            IUnitOfWork context = new HQContext();

            ISkillService skillService = new SkillService(context);
            SkillRequirement requirement = skillService.GetSkillRequirement(id);

            skillService.DeleteRequirement(requirement);
            context.Save();

            return RedirectToAction("edit", new { slug = slug });
        }

        #region DeleteBonus
        /// <summary>
        /// Delete a skill bonus.
        /// </summary>
        /// <param name="slug">The parent skill slug</param>
        /// <param name="id">The id of the bonus.</param>
        /// <returns></returns>
        public ActionResult DeleteBonus(string slug, int id)
        {
            IUnitOfWork context = new HQContext();

            IBonusService bonusService = new BonusService(context);
            bonusService.DeleteBonus(id);

            context.Save();

            return RedirectToAction("edit", new { slug = slug });
        }
        #endregion

        #region AddSkillRequirement
        public ActionResult AddSkillRequirement(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            int skillId = 0;

            string skill = Request.Form["SkillList"];
            string combination = Request.Form["combination"];
            string qty = Request.Form["quantity"];

            int quantity = 1;

            int.TryParse(qty, out quantity);

            RequirementCombination combo = (RequirementCombination)Enum.Parse(typeof(RequirementCombination), combination, true);

            if (skill != null && int.TryParse(skill, out skillId))
            {
                ISkillService skillService = new SkillService(context);

                Skill parentSkill = skillService.GetSkill(ruleSetId, slug);

                skillService.CreateRequirement(parentSkill.SkillID, skillId, null, null, false, quantity, combo, null);

                context.Save();
            }

            return RedirectToAction("edit", new { slug = slug });
        }
        #endregion

        #region AddGuildRequirement
        public ActionResult AddGuildRequirement(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            int guildId = 0;

            string guild = Request.Form["GuildList"];
            bool primaryOnly = Request.Form["PrimaryGuildOnly"] != null && Request.Form["PrimaryGuildOnly"].Contains("true");
            int? guildLevel = null;

            string rank = Request.Form["guildLevel"];

            int value = 0;
            if (int.TryParse(rank, out value))
            {
                guildLevel = value;
            }

            if (guild != null && int.TryParse(guild, out guildId))
            {
                ISkillService skillService = new SkillService(context);

                Skill parentSkill = skillService.GetSkill(ruleSetId, slug);

                skillService.CreateRequirement(parentSkill.SkillID, null, guildId, null, primaryOnly, null, RequirementCombination.OR, guildLevel);

                context.Save();
            }

            return RedirectToAction("edit", new { slug = slug });
        }
        #endregion

        #region AddRankRequirement
        public ActionResult AddRankRequirement(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();
            int minRank = 0;

            string rank = Request.Form["minRank"];
            string combination = Request.Form["combination"];

            RequirementCombination combo = (RequirementCombination)Enum.Parse(typeof(RequirementCombination), combination, true);

            if (rank != null && int.TryParse(rank, out minRank))
            {
                ISkillService skillService = new SkillService(context);

                Skill parentSkill = skillService.GetSkill(ruleSetId, slug);

                skillService.CreateRequirement(parentSkill.SkillID, null, null, minRank, false, null, combo, null);

                context.Save();
            }

            return RedirectToAction("edit", new { slug = slug });
        }
        #endregion

        #region AddSkillBonus
        public ActionResult AddSkillBonus(int ruleSetId, string slug)
        {
            IUnitOfWork context = new HQContext();

            string typeString = Request.Form["BonusTypes"];
            string qtyString = Request.Form["QuantityList"];
            string valString = Request.Form["value"];
            string reqString = Request.Form["requirement"];

            BonusTypes type = BonusTypes.ArmouredDex;

            if (!Enum.TryParse<BonusTypes>(typeString, out type))
            {
                ModelState.AddModelError("BonusType", "Invalid bonus type.");
            }

            BonusRequirement requirement = BonusRequirement.None;

            if (!Enum.TryParse<BonusRequirement>(reqString, out requirement))
            {
                ModelState.AddModelError("BonusRequirement", "Invalid bonus requirement type.");
            }


            int quantity = 1;
            if (!int.TryParse(qtyString, out quantity))
            {
                ModelState.AddModelError("Quantity", "Invalid quantity value.");
            }


            ISkillService skillService = new SkillService(context);
            Skill skill = skillService.GetSkill(ruleSetId, slug);

            IBonusService bonusService = new BonusService(context);
            bonusService.CreateBonus(skill.SkillID, type, quantity, valString, requirement);

            context.Save();

            return RedirectToAction("edit", new { slug = slug });
        }
        #endregion

        private void PopulateCostData(Skill skill)
        {
            List<SkillCost> allCosts = skill.Costs.ToList();
            ViewBag.Costs = allCosts;
        }

        private void PopulateRequirementsData(IUnitOfWork context, Skill skill)
        {
            ISkillService skillService = new SkillService(context);
            List<SkillRequirement> reqs = skillService.GetSkillRequirements(skill.SkillID).ToList();
            ViewBag.Requirements = reqs;
        }

        #region UpdateCosts
        private bool UpdateCosts(IUnitOfWork context, Skill skill, FormCollection formCollection)
        {
            ISkillService skillService = new SkillService(context);
            List<SkillCost> costs = skillService.GetSkillCosts(skill.SkillID).ToList();

            foreach (SkillCost cost in costs)
            {
                // key cost.
                string costKey = string.Format("cost_{0}", cost.SkillCostID);
                int costCost = Convert.ToInt32(formCollection.Get(costKey));
                cost.Cost = costCost;

                // factor key
                string modKey = string.Format("fac_{0}", cost.SkillCostID);
                int costMod = Convert.ToInt32(formCollection.Get(modKey));
                cost.Factor = costMod;

                if (!TryValidateModel(cost))
                {
                    ModelState.AddModelError("", "Invalid skill cost value");
                    return false;
                }

                skillService.UpdateSkillCost(cost);
            }
            return true;
        }
        #endregion

        #region CreateCosts
        /// <summary>Create new skill costs for a new skill.</summary>
        /// <param name="skill"></param>
        /// <param name="formCollection"></param>
        /// <param name="ruleSetId"></param>
        /// <returns></returns>
        private bool CreateCosts(IUnitOfWork context, Skill skill, FormCollection formCollection, int ruleSetId)
        {
            ISkillService skillService = new SkillService(context);

            List<PointsTable> tables = skillService.GetPointsTables(ruleSetId).ToList();

            foreach (PointsTable table in tables)
            {
                SkillCost cost = new SkillCost() {PointsTableID = table.PointsTableID};

                // key cost.
                string costKey = string.Format("cost_{0}", table.PointsTableID);
                int costCost = Convert.ToInt32(formCollection.Get(costKey));
                cost.Cost = costCost;

                // factor key
                string modKey = string.Format("fac_{0}", cost.SkillCostID);
                int costMod = Convert.ToInt32(formCollection.Get(modKey));
                cost.Factor = costMod;

                if (!TryValidateModel(cost))
                {
                    ModelState.AddModelError("", "Invalid skill cost value");
                    return false;
                }

                if (skill.Costs == null)
                    skill.Costs = new List<SkillCost>();

                skill.Costs.Add(cost);
            }

            return true;
        }
        #endregion

        private void PopulateSkillTypeDropdown(object selectedType = null)
        {
            var types = new List<string>() { "Martial", "Magic", "Power", "Scout", "Ki", "Adrenal", "Psi" };
            ViewBag.TypeOfSkill = new SelectList(types, selectedType);
        }

        private void PopulateBonusTypeDropdown(object selectedType = null)
        {
            var types = Enum.GetNames(typeof(BonusTypes));
            ViewBag.BonusTypes = new SelectList(types, selectedType);
        }

        #region PopulateSkillListDropdown
        /// <summary>
        /// Create a list of all skills excluding the current skill and any skills
        /// that are already requirements of it.
        /// </summary>
        /// <param name="omittedSkill">The parent skill to omit.</param>
        private void PopulateSkillListDropdown(IUnitOfWork context, Skill omittedSkill)
        {
            ISkillService skillService = new SkillService(context);

            List<int> existingRequiredSkills =
                skillService.GetSkillRequirements(omittedSkill.SkillID)
                .Where(r => r.RequiredSkillID.HasValue)
                .Select(r => r.RequiredSkillID.Value)
                .ToList();

            var skills =
                skillService.GetRuleSkills(omittedSkill.RuleSetID)
                .Where(s => s.SkillID != omittedSkill.SkillID && !existingRequiredSkills.Contains(s.SkillID))
                .ToList();

            ViewBag.SkillList = new SelectList(skills, "SkillID", "Name");
        }
        #endregion

        #region PopulateGuildListDropdown
        /// <summary>
        /// Create a list of all guilds excluding guilds
        /// that are already requirements of the current skill.
        /// </summary>
        /// <param name="omittedSkill">The parent skill to omit.</param>
        private void PopulateGuildListDropdown(IUnitOfWork context, Skill skill)
        {
            IGuildService guildService = new GuildService(context);
            ISkillService skillService = new SkillService(context);

            List<int> existingRequiredGuilds =
                skillService.GetSkillRequirements(skill.SkillID)
                .Where(s => s.GuildID.HasValue)
                .Select(r => r.GuildID.Value)
                .ToList();

            var guilds =
                guildService.GetGuilds(skill.RuleSetID)
                .Where(g => !existingRequiredGuilds.Contains(g.GuildID))
                .ToList();

            ViewBag.GuildList = new SelectList(guilds, "GuildID", "Name");
        }
        #endregion

        #region PopulateSkillBonusValueTypeList
        /// <summary>
        /// When adding a new skill bonus some require a further selection of skill slug or spell level etc.
        /// </summary>
        private void PopulateSkillBonusValueTypeList(IUnitOfWork context, int ruleSetId)
        {
            ISkillService skillService = new SkillService(context);

            SkillBonusValueListModel values = new SkillBonusValueListModel();

            // Add list of skills keyed to MaxSkillRanks bonus.
            var skills = skillService.GetRuleSkills(ruleSetId);

            foreach (Skill skill in skills)
            {
                SkillBonusValueItemModel model = new SkillBonusValueItemModel()
                {
                    Key = Enum.GetName(typeof(BonusTypes), BonusTypes.MaxSkillRanks),
                    Name = skill.Name,
                    Value = skill.Slug
                };

                values.Add(model);
            }

            // Add list of spell levels for SpellLevel skill bonus type.
            for (int i = 1; i <= 10; i++)
            {
                SkillBonusValueItemModel model = new SkillBonusValueItemModel()
                {
                    Key = Enum.GetName(typeof(BonusTypes), BonusTypes.SpellLevel),
                    Name = string.Format("Spell level {0}", i) ,
                    Value = i.ToString()
                };

                values.Add(model);
            }

            ViewBag.SkillBonusValueList = values;
        }
        #endregion

        private void PopulateQuantityList()
        {
            List<object> values = new List<object>();

            for (int i = 1; i <= 100; i++)
                values.Add(new { Text = i.ToString(), Value = i.ToString() });

            ViewBag.QuantityList = new SelectList(values, "Value", "Text");
        }
    }
}